This notebook is all about benchmarking some R code used in this package.

Hardware / Software used:

  • Intel i7-4600U
  • Compilation flags for C/C++: -O2 -Wall $(DEBUGFLAG) -mtune=core2 (R’s defaults)
  • Windows Server 2012 R2
  • R 3.3.2 + Intel MKL

Libraries

library(data.table)
data.table 1.10.4
  The fastest way to learn (by data.table authors): https://www.datacamp.com/courses/data-analysis-the-data-table-way
  Documentation: ?data.table, example(data.table) and browseVignettes("data.table")
  Release notes, videos and slides: http://r-datatable.com
library(microbenchmark)
library(Rcpp)
library(ggplot2)
library(plotly)

Attaching package: <U+393C><U+3E31>plotly<U+393C><U+3E32>

The following object is masked from <U+393C><U+3E31>package:ggplot2<U+393C><U+3E32>:

    last_plot

The following object is masked from <U+393C><U+3E31>package:stats<U+393C><U+3E32>:

    filter

The following object is masked from <U+393C><U+3E31>package:graphics<U+393C><U+3E32>:

    layout
# Helper function to print data well in tables
print_well <- function(data, digits = 6) {
  
  # To milliseconds
  data <- data / 1000000
  
  # Sprintf helper
  sprintf_helper <- paste0("%.0", digits, "f")
  
  cat("| Min | 25% | 50% | 75% | Max | Mean |  \n| --: | --: | --: | --: | --: | --: |  \n| ", sprintf(sprintf_helper, min(data)), " | ", sprintf(sprintf_helper, quantile(data, probs = 0.25)), " | ", sprintf(sprintf_helper, median(data)), " | ", sprintf(sprintf_helper, quantile(data, probs = 0.75)), " | ", sprintf(sprintf_helper, max(data)), " | ", sprintf(sprintf_helper, mean(data)), " |  \n", sep = "")
  
  return(data)
  
}
# Test case function
# Arguments renamed to avoid recursive clash
test_case <- function(f, preds, labels, eps) {
  cat("Test case: ", paste(do.call(f, list(preds = preds[1:50],
                                           labels = labels[1:5])), collapse = ", "), "  \n")
}

Benchmarking Vector to Matrix to Vector

For a 10-class vector of 100,000 observations:

  • Vector A of length=(100000 * 10)
  • Vector B of length=(100000) with 10 classes
A = [1:1, 1:2, 1:3, 1:4... 1:10, 2:1, 2:2, 2:3..., 100000:8, 100000:9, 100000:10]
B = [3, 5, 9, 1, 4, 8, 6, ...]

Get the following Vector C:

C = [1:4, 2:6, 3:10, 4:2, 5:5, 6:9, 7:7, ...]

Initialize data

# Generate random data
set.seed(11111)
data <- runif(10000000, 0, 1)
labels <- round(runif(1000000, 0, 9), digits = 0)
# Background truth example
array(data[1:50], dim = c(10, 5))
           [,1]       [,2]       [,3]       [,4]       [,5]
 [1,] 0.5014483 0.57219649 0.70236924 0.06440384 0.78924978
 [2,] 0.9702328 0.34292525 0.50889166 0.65780657 0.72449914
 [3,] 0.7876004 0.09627503 0.20268701 0.19973930 0.00886554
 [4,] 0.9022259 0.74235690 0.90706612 0.24664551 0.57348710
 [5,] 0.8141778 0.42274539 0.05441064 0.25997440 0.73034459
 [6,] 0.7998922 0.98402494 0.09045308 0.14869691 0.42458612
 [7,] 0.1158690 0.89258437 0.85759800 0.21375685 0.07684022
 [8,] 0.7171363 0.59541565 0.94780036 0.20598170 0.53318628
 [9,] 0.1020639 0.80531234 0.16479666 0.02075780 0.28521238
[10,] 0.6856938 0.42102379 0.42536097 0.63880218 0.86408083
labels[1:5]
[1] 5 1 1 7 8
data[c(1 + labels[1], 11 + labels[2], 21 + labels[3], 31 + labels[4], 41 + labels[5])]
[1] 0.7998922 0.3429253 0.5088917 0.2059817 0.2852124
# How many digits for benchmarking in milliseconds
my_digits <- 6L
# How many runs for benchmarking?
my_runs <- 1000L

Benchmarks

# ===== BLOCK 1 =====
faster1 <- function(preds, labels) {
  data_len <- length(preds)
  label_len <- length(labels)
  len_div <- data_len / label_len
  dims <- c(len_div, label_len)
  label_long <- labels + 1
  mat_out <- cbind(label_long, 1:label_len)
  vec_out <- array(preds, dim = dims)[mat_out]
  return(vec_out)
}
test_case(faster1, preds = data, labels = labels)

Test case: 0.799892218317837, 0.342925252625719, 0.508891655597836, 0.205981696723029, 0.285212381277233

data1 <- print_well(microbenchmark(faster1(data, labels), times = my_runs)$time, digits = my_digits)
Min 25% 50% 75% Max Mean
64.312658 76.900123 82.223366 91.294758 187.028369 94.528352
# ===== BLOCK 2 =====
faster2 <- function(preds, labels) {
  data_len <- length(preds)
  label_len <- length(labels)
  vec_out <- array(preds, dim = c(data_len / label_len, label_len))[cbind(labels + 1, 1:label_len)]
  return(vec_out)
}
test_case(faster2, preds = data, labels = labels)

Test case: 0.799892218317837, 0.342925252625719, 0.508891655597836, 0.205981696723029, 0.285212381277233

data2 <- print_well(microbenchmark(faster2(data, labels), times = my_runs)$time, digits = my_digits)
Min 25% 50% 75% Max Mean
75.522127 77.650702 79.183414 83.235100 164.776690 85.804421
# ===== BLOCK 3 =====
faster3 <- function(preds, labels) {
  return(array(preds, dim = c(length(preds) / length(labels), length(labels)))[cbind(labels + 1, 1:length(labels))])
}
test_case(faster3, preds = data, labels = labels)

Test case: 0.799892218317837, 0.342925252625719, 0.508891655597836, 0.205981696723029, 0.285212381277233

data3 <- print_well(microbenchmark(faster3(data, labels), times = my_runs)$time, digits = my_digits)
Min 25% 50% 75% Max Mean
65.931280 77.665623 79.225228 82.958931 160.472404 82.804429
# ===== BLOCK 4 =====
faster4 <- function(preds, labels) {
  return(array(preds, dim = c(length(preds) / length(labels), length(labels)))[matrix(c(labels + 1, 1:length(labels)), ncol = 2)])
}
test_case(faster4, preds = data, labels = labels)

Test case: 0.799892218317837, 0.342925252625719, 0.508891655597836, 0.205981696723029, 0.285212381277233

data4 <- print_well(microbenchmark(faster4(data, labels), times = my_runs)$time, digits = my_digits)
Min 25% 50% 75% Max Mean
74.609039 87.599544 89.413651 93.436732 172.448608 93.591531
# ===== BLOCK 5 =====
faster5 <- function(preds, labels) {
  return(preds[((0:(length(labels) - 1)) * (length(preds) / length(labels))) + labels + 1])
}
test_case(faster5, preds = data, labels = labels)

Test case: 0.799892218317837, 0.342925252625719, 0.508891655597836, 0.205981696723029, 0.285212381277233

data5 <- print_well(microbenchmark(faster5(data, labels), times = my_runs)$time, digits = my_digits)
Min 25% 50% 75% Max Mean
23.954310 24.815225 25.280228 29.532910 118.538770 28.823157
# ===== BLOCK 6 =====
cppFunction("NumericVector faster6(NumericVector preds, NumericVector labels) {
  int labels_size = labels.size();
  NumericVector selected(labels_size);
  selected = (preds.size() / labels_size) * seq(0, labels_size - 1);
  selected = selected + labels;
  NumericVector to_return(labels_size);
  to_return = preds[selected];
  return to_return;
}")
test_case(faster6, preds = data, labels = labels)

Test case: 0.799892218317837, 0.342925252625719, 0.508891655597836, 0.205981696723029, 0.285212381277233

data6 <- print_well(microbenchmark(faster6(data, labels), times = my_runs)$time, digits = my_digits)
Min 25% 50% 75% Max Mean
21.357217 22.161396 22.644360 27.302364 106.357671 26.335169

Summary Results

data_time <- data.table(rbindlist(list(data.frame(Time = data1, Bench = "faster1"),
                                       data.frame(Time = data2, Bench = "faster2"),
                                       data.frame(Time = data3, Bench = "faster3"),
                                       data.frame(Time = data4, Bench = "faster4"),
                                       data.frame(Time = data5, Bench = "faster5"),
                                       data.frame(Time = data6, Bench = "faster6"))))
data_time <- data_time[, t_mean := mean(Time), by = Bench]
data_time <- data_time[, t_median := median(Time), by = Bench]
data_time$Benchs <- data_time$Bench 
levels(data_time$Benchs) <- paste0("faster", 1:6, "= [", sprintf(paste0("%.0", my_digits, "f"), data_time[, list(min(Time)), by = Bench]$V1), ", ", sprintf(paste0("%.0", my_digits, "f"), data_time[, list(max(Time)), by = Bench]$V1), "], mean=", sprintf(paste0("%.0", my_digits, "f"), data_time[, list(mean(Time)), by = Bench]$V1), ", median=", sprintf(paste0("%.0", my_digits, "f"), data_time[, list(median(Time)), by = Bench]$V1))
my_time <- data_time[, list(min(Time), quantile(Time, probs = 0.25), median(Time), quantile(Time, probs = 0.75), max(Time), mean(Time)), by = Bench]
colnames(my_time) <- c("Function", "Min", "25%", "50%", "75%", "Max", "Mean")
my_time <- my_time[order(Mean, decreasing = FALSE), ]
print(my_time, digits = 6)

Plot Results

ggplotly(ggplot(data = data_time, aes(x = Time)) + geom_histogram(aes(y = ..density..), bins = 20, color = "darkblue", fill = "lightblue") + facet_wrap(~ Benchs, ncol = 2) + geom_vline(aes(xintercept = t_mean), color = "blue", linetype = "dashed", size = 2) + geom_vline(aes(xintercept = t_median), color = "red", linetype = "dashed", size = 2) + labs(x = "Time (Milliseconds)", y = "Density") + theme_bw())
ggplotly(ggplot(data = data_time[, .(Time, Bench)], aes(x = Time, y = ..count.., fill = Bench)) + geom_histogram(aes(y = ..density..), bins = 100, position = "fill") + labs(x = "Time (Milliseconds)", y = "Density") + theme_bw())
ggplotly(ggplot(data = data_time, aes(x = Time, y = ..count.., fill = Bench)) + geom_density(position = "fill") + labs(x = "Time (Milliseconds)", y = "Density") + theme_bw())
LS0tDQp0aXRsZTogIkJlbmNobWFya3M6IHZlY3QybWF0MnZlY3QiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgY29sbGFwc2VkOiBubw0KICAgIHRoZW1lOiB1bml0ZWQNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNg0KICAgIHRvY19mbG9hdDogeWVzDQotLS0NCg0KVGhpcyBub3RlYm9vayBpcyBhbGwgYWJvdXQgYmVuY2htYXJraW5nIHNvbWUgUiBjb2RlIHVzZWQgaW4gdGhpcyBwYWNrYWdlLg0KDQpIYXJkd2FyZSAvIFNvZnR3YXJlIHVzZWQ6DQoNCiogSW50ZWwgaTctNDYwMFUNCiogQ29tcGlsYXRpb24gZmxhZ3MgZm9yIEMvQysrOiBgLU8yIC1XYWxsICQoREVCVUdGTEFHKSAtbXR1bmU9Y29yZTJgIChSJ3MgZGVmYXVsdHMpDQoqIFdpbmRvd3MgU2VydmVyIDIwMTIgUjINCiogUiAzLjMuMiArIEludGVsIE1LTA0KDQojIExpYnJhcmllcw0KDQpgYGB7ciBpbml0fQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShtaWNyb2JlbmNobWFyaykNCmxpYnJhcnkoUmNwcCkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkocGxvdGx5KQ0KYGBgDQoNCmBgYHtyIGJhc2VkfQ0KDQojIEhlbHBlciBmdW5jdGlvbiB0byBwcmludCBkYXRhIHdlbGwgaW4gdGFibGVzDQpwcmludF93ZWxsIDwtIGZ1bmN0aW9uKGRhdGEsIGRpZ2l0cyA9IDYpIHsNCiAgDQogICMgVG8gbWlsbGlzZWNvbmRzDQogIGRhdGEgPC0gZGF0YSAvIDEwMDAwMDANCiAgDQogICMgU3ByaW50ZiBoZWxwZXINCiAgc3ByaW50Zl9oZWxwZXIgPC0gcGFzdGUwKCIlLjAiLCBkaWdpdHMsICJmIikNCiAgDQogIGNhdCgifCBNaW4gfCAyNSUgfCA1MCUgfCA3NSUgfCBNYXggfCBNZWFuIHwgIFxufCAtLTogfCAtLTogfCAtLTogfCAtLTogfCAtLTogfCAtLTogfCAgXG58ICIsIHNwcmludGYoc3ByaW50Zl9oZWxwZXIsIG1pbihkYXRhKSksICIgfCAiLCBzcHJpbnRmKHNwcmludGZfaGVscGVyLCBxdWFudGlsZShkYXRhLCBwcm9icyA9IDAuMjUpKSwgIiB8ICIsIHNwcmludGYoc3ByaW50Zl9oZWxwZXIsIG1lZGlhbihkYXRhKSksICIgfCAiLCBzcHJpbnRmKHNwcmludGZfaGVscGVyLCBxdWFudGlsZShkYXRhLCBwcm9icyA9IDAuNzUpKSwgIiB8ICIsIHNwcmludGYoc3ByaW50Zl9oZWxwZXIsIG1heChkYXRhKSksICIgfCAiLCBzcHJpbnRmKHNwcmludGZfaGVscGVyLCBtZWFuKGRhdGEpKSwgIiB8ICBcbiIsIHNlcCA9ICIiKQ0KICANCiAgcmV0dXJuKGRhdGEpDQogIA0KfQ0KDQojIFRlc3QgY2FzZSBmdW5jdGlvbg0KIyBBcmd1bWVudHMgcmVuYW1lZCB0byBhdm9pZCByZWN1cnNpdmUgY2xhc2gNCnRlc3RfY2FzZSA8LSBmdW5jdGlvbihmLCBwcmVkcywgbGFiZWxzLCBlcHMpIHsNCiAgY2F0KCJUZXN0IGNhc2U6ICIsIHBhc3RlKGRvLmNhbGwoZiwgbGlzdChwcmVkcyA9IHByZWRzWzE6NTBdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGxhYmVsc1sxOjVdKSksIGNvbGxhcHNlID0gIiwgIiksICIgIFxuIikNCn0NCg0KYGBgDQoNCiMgQmVuY2htYXJraW5nIFZlY3RvciB0byBNYXRyaXggdG8gVmVjdG9yDQoNCkZvciBhIDEwLWNsYXNzIHZlY3RvciBvZiAxMDAsMDAwIG9ic2VydmF0aW9uczoNCg0KKiBWZWN0b3IgQSBvZiBsZW5ndGg9KDEwMDAwMCAqIDEwKQ0KKiBWZWN0b3IgQiBvZiBsZW5ndGg9KDEwMDAwMCkgd2l0aCAxMCBjbGFzc2VzDQoNCmBgYA0KQSA9IFsxOjEsIDE6MiwgMTozLCAxOjQuLi4gMToxMCwgMjoxLCAyOjIsIDI6My4uLiwgMTAwMDAwOjgsIDEwMDAwMDo5LCAxMDAwMDA6MTBdDQpCID0gWzMsIDUsIDksIDEsIDQsIDgsIDYsIC4uLl0NCmBgYA0KDQpHZXQgdGhlIGZvbGxvd2luZyBWZWN0b3IgQzoNCg0KYGBgDQpDID0gWzE6NCwgMjo2LCAzOjEwLCA0OjIsIDU6NSwgNjo5LCA3OjcsIC4uLl0NCmBgYA0KDQojIEluaXRpYWxpemUgZGF0YQ0KDQpgYGB7ciBiZW5jaDF9DQojIEdlbmVyYXRlIHJhbmRvbSBkYXRhDQpzZXQuc2VlZCgxMTExMSkNCmRhdGEgPC0gcnVuaWYoMTAwMDAwMDAsIDAsIDEpDQpsYWJlbHMgPC0gcm91bmQocnVuaWYoMTAwMDAwMCwgMCwgOSksIGRpZ2l0cyA9IDApDQoNCiMgQmFja2dyb3VuZCB0cnV0aCBleGFtcGxlDQphcnJheShkYXRhWzE6NTBdLCBkaW0gPSBjKDEwLCA1KSkNCmxhYmVsc1sxOjVdDQpkYXRhW2MoMSArIGxhYmVsc1sxXSwgMTEgKyBsYWJlbHNbMl0sIDIxICsgbGFiZWxzWzNdLCAzMSArIGxhYmVsc1s0XSwgNDEgKyBsYWJlbHNbNV0pXQ0KDQojIEhvdyBtYW55IGRpZ2l0cyBmb3IgYmVuY2htYXJraW5nIGluIG1pbGxpc2Vjb25kcw0KbXlfZGlnaXRzIDwtIDZMDQoNCiMgSG93IG1hbnkgcnVucyBmb3IgYmVuY2htYXJraW5nPw0KbXlfcnVucyA8LSAxMDAwTA0KYGBgDQoNCiMgQmVuY2htYXJrcw0KDQpgYGB7ciBiZW5jaDIsIHJlc3VsdHM9ImFzaXMifQ0KDQojID09PT09IEJMT0NLIDEgPT09PT0NCmZhc3RlcjEgPC0gZnVuY3Rpb24ocHJlZHMsIGxhYmVscykgew0KICBkYXRhX2xlbiA8LSBsZW5ndGgocHJlZHMpDQogIGxhYmVsX2xlbiA8LSBsZW5ndGgobGFiZWxzKQ0KICBsZW5fZGl2IDwtIGRhdGFfbGVuIC8gbGFiZWxfbGVuDQogIGRpbXMgPC0gYyhsZW5fZGl2LCBsYWJlbF9sZW4pDQogIGxhYmVsX2xvbmcgPC0gbGFiZWxzICsgMQ0KICBtYXRfb3V0IDwtIGNiaW5kKGxhYmVsX2xvbmcsIDE6bGFiZWxfbGVuKQ0KICB2ZWNfb3V0IDwtIGFycmF5KHByZWRzLCBkaW0gPSBkaW1zKVttYXRfb3V0XQ0KICByZXR1cm4odmVjX291dCkNCn0NCnRlc3RfY2FzZShmYXN0ZXIxLCBwcmVkcyA9IGRhdGEsIGxhYmVscyA9IGxhYmVscykNCmRhdGExIDwtIHByaW50X3dlbGwobWljcm9iZW5jaG1hcmsoZmFzdGVyMShkYXRhLCBsYWJlbHMpLCB0aW1lcyA9IG15X3J1bnMpJHRpbWUsIGRpZ2l0cyA9IG15X2RpZ2l0cykNCg0KIyA9PT09PSBCTE9DSyAyID09PT09DQpmYXN0ZXIyIDwtIGZ1bmN0aW9uKHByZWRzLCBsYWJlbHMpIHsNCiAgZGF0YV9sZW4gPC0gbGVuZ3RoKHByZWRzKQ0KICBsYWJlbF9sZW4gPC0gbGVuZ3RoKGxhYmVscykNCiAgdmVjX291dCA8LSBhcnJheShwcmVkcywgZGltID0gYyhkYXRhX2xlbiAvIGxhYmVsX2xlbiwgbGFiZWxfbGVuKSlbY2JpbmQobGFiZWxzICsgMSwgMTpsYWJlbF9sZW4pXQ0KICByZXR1cm4odmVjX291dCkNCn0NCnRlc3RfY2FzZShmYXN0ZXIyLCBwcmVkcyA9IGRhdGEsIGxhYmVscyA9IGxhYmVscykNCmRhdGEyIDwtIHByaW50X3dlbGwobWljcm9iZW5jaG1hcmsoZmFzdGVyMihkYXRhLCBsYWJlbHMpLCB0aW1lcyA9IG15X3J1bnMpJHRpbWUsIGRpZ2l0cyA9IG15X2RpZ2l0cykNCg0KIyA9PT09PSBCTE9DSyAzID09PT09DQpmYXN0ZXIzIDwtIGZ1bmN0aW9uKHByZWRzLCBsYWJlbHMpIHsNCiAgcmV0dXJuKGFycmF5KHByZWRzLCBkaW0gPSBjKGxlbmd0aChwcmVkcykgLyBsZW5ndGgobGFiZWxzKSwgbGVuZ3RoKGxhYmVscykpKVtjYmluZChsYWJlbHMgKyAxLCAxOmxlbmd0aChsYWJlbHMpKV0pDQp9DQp0ZXN0X2Nhc2UoZmFzdGVyMywgcHJlZHMgPSBkYXRhLCBsYWJlbHMgPSBsYWJlbHMpDQpkYXRhMyA8LSBwcmludF93ZWxsKG1pY3JvYmVuY2htYXJrKGZhc3RlcjMoZGF0YSwgbGFiZWxzKSwgdGltZXMgPSBteV9ydW5zKSR0aW1lLCBkaWdpdHMgPSBteV9kaWdpdHMpDQoNCiMgPT09PT0gQkxPQ0sgNCA9PT09PQ0KZmFzdGVyNCA8LSBmdW5jdGlvbihwcmVkcywgbGFiZWxzKSB7DQogIHJldHVybihhcnJheShwcmVkcywgZGltID0gYyhsZW5ndGgocHJlZHMpIC8gbGVuZ3RoKGxhYmVscyksIGxlbmd0aChsYWJlbHMpKSlbbWF0cml4KGMobGFiZWxzICsgMSwgMTpsZW5ndGgobGFiZWxzKSksIG5jb2wgPSAyKV0pDQp9DQp0ZXN0X2Nhc2UoZmFzdGVyNCwgcHJlZHMgPSBkYXRhLCBsYWJlbHMgPSBsYWJlbHMpDQpkYXRhNCA8LSBwcmludF93ZWxsKG1pY3JvYmVuY2htYXJrKGZhc3RlcjQoZGF0YSwgbGFiZWxzKSwgdGltZXMgPSBteV9ydW5zKSR0aW1lLCBkaWdpdHMgPSBteV9kaWdpdHMpDQoNCiMgPT09PT0gQkxPQ0sgNSA9PT09PQ0KZmFzdGVyNSA8LSBmdW5jdGlvbihwcmVkcywgbGFiZWxzKSB7DQogIHJldHVybihwcmVkc1soKDA6KGxlbmd0aChsYWJlbHMpIC0gMSkpICogKGxlbmd0aChwcmVkcykgLyBsZW5ndGgobGFiZWxzKSkpICsgbGFiZWxzICsgMV0pDQp9DQp0ZXN0X2Nhc2UoZmFzdGVyNSwgcHJlZHMgPSBkYXRhLCBsYWJlbHMgPSBsYWJlbHMpDQpkYXRhNSA8LSBwcmludF93ZWxsKG1pY3JvYmVuY2htYXJrKGZhc3RlcjUoZGF0YSwgbGFiZWxzKSwgdGltZXMgPSBteV9ydW5zKSR0aW1lLCBkaWdpdHMgPSBteV9kaWdpdHMpDQoNCiMgPT09PT0gQkxPQ0sgNiA9PT09PQ0KY3BwRnVuY3Rpb24oIk51bWVyaWNWZWN0b3IgZmFzdGVyNihOdW1lcmljVmVjdG9yIHByZWRzLCBOdW1lcmljVmVjdG9yIGxhYmVscykgew0KICBpbnQgbGFiZWxzX3NpemUgPSBsYWJlbHMuc2l6ZSgpOw0KICBOdW1lcmljVmVjdG9yIHNlbGVjdGVkKGxhYmVsc19zaXplKTsNCiAgc2VsZWN0ZWQgPSAocHJlZHMuc2l6ZSgpIC8gbGFiZWxzX3NpemUpICogc2VxKDAsIGxhYmVsc19zaXplIC0gMSk7DQogIHNlbGVjdGVkID0gc2VsZWN0ZWQgKyBsYWJlbHM7DQogIE51bWVyaWNWZWN0b3IgdG9fcmV0dXJuKGxhYmVsc19zaXplKTsNCiAgdG9fcmV0dXJuID0gcHJlZHNbc2VsZWN0ZWRdOw0KICByZXR1cm4gdG9fcmV0dXJuOw0KfSIpDQp0ZXN0X2Nhc2UoZmFzdGVyNiwgcHJlZHMgPSBkYXRhLCBsYWJlbHMgPSBsYWJlbHMpDQpkYXRhNiA8LSBwcmludF93ZWxsKG1pY3JvYmVuY2htYXJrKGZhc3RlcjYoZGF0YSwgbGFiZWxzKSwgdGltZXMgPSBteV9ydW5zKSR0aW1lLCBkaWdpdHMgPSBteV9kaWdpdHMpDQoNCmBgYA0KDQojIFN1bW1hcnkgUmVzdWx0cw0KDQpgYGB7ciBiZW5jaDN9DQoNCmRhdGFfdGltZSA8LSBkYXRhLnRhYmxlKHJiaW5kbGlzdChsaXN0KGRhdGEuZnJhbWUoVGltZSA9IGRhdGExLCBCZW5jaCA9ICJmYXN0ZXIxIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLmZyYW1lKFRpbWUgPSBkYXRhMiwgQmVuY2ggPSAiZmFzdGVyMiIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YS5mcmFtZShUaW1lID0gZGF0YTMsIEJlbmNoID0gImZhc3RlcjMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEuZnJhbWUoVGltZSA9IGRhdGE0LCBCZW5jaCA9ICJmYXN0ZXI0IiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLmZyYW1lKFRpbWUgPSBkYXRhNSwgQmVuY2ggPSAiZmFzdGVyNSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YS5mcmFtZShUaW1lID0gZGF0YTYsIEJlbmNoID0gImZhc3RlcjYiKSkpKQ0KZGF0YV90aW1lIDwtIGRhdGFfdGltZVssIHRfbWVhbiA6PSBtZWFuKFRpbWUpLCBieSA9IEJlbmNoXQ0KZGF0YV90aW1lIDwtIGRhdGFfdGltZVssIHRfbWVkaWFuIDo9IG1lZGlhbihUaW1lKSwgYnkgPSBCZW5jaF0NCmRhdGFfdGltZSRCZW5jaHMgPC0gZGF0YV90aW1lJEJlbmNoIA0KbGV2ZWxzKGRhdGFfdGltZSRCZW5jaHMpIDwtIHBhc3RlMCgiZmFzdGVyIiwgMTo2LCAiPSBbIiwgc3ByaW50ZihwYXN0ZTAoIiUuMCIsIG15X2RpZ2l0cywgImYiKSwgZGF0YV90aW1lWywgbGlzdChtaW4oVGltZSkpLCBieSA9IEJlbmNoXSRWMSksICIsICIsIHNwcmludGYocGFzdGUwKCIlLjAiLCBteV9kaWdpdHMsICJmIiksIGRhdGFfdGltZVssIGxpc3QobWF4KFRpbWUpKSwgYnkgPSBCZW5jaF0kVjEpLCAiXSwgbWVhbj0iLCBzcHJpbnRmKHBhc3RlMCgiJS4wIiwgbXlfZGlnaXRzLCAiZiIpLCBkYXRhX3RpbWVbLCBsaXN0KG1lYW4oVGltZSkpLCBieSA9IEJlbmNoXSRWMSksICIsIG1lZGlhbj0iLCBzcHJpbnRmKHBhc3RlMCgiJS4wIiwgbXlfZGlnaXRzLCAiZiIpLCBkYXRhX3RpbWVbLCBsaXN0KG1lZGlhbihUaW1lKSksIGJ5ID0gQmVuY2hdJFYxKSkNCg0KbXlfdGltZSA8LSBkYXRhX3RpbWVbLCBsaXN0KG1pbihUaW1lKSwgcXVhbnRpbGUoVGltZSwgcHJvYnMgPSAwLjI1KSwgbWVkaWFuKFRpbWUpLCBxdWFudGlsZShUaW1lLCBwcm9icyA9IDAuNzUpLCBtYXgoVGltZSksIG1lYW4oVGltZSkpLCBieSA9IEJlbmNoXQ0KY29sbmFtZXMobXlfdGltZSkgPC0gYygiRnVuY3Rpb24iLCAiTWluIiwgIjI1JSIsICI1MCUiLCAiNzUlIiwgIk1heCIsICJNZWFuIikNCm15X3RpbWUgPC0gbXlfdGltZVtvcmRlcihNZWFuLCBkZWNyZWFzaW5nID0gRkFMU0UpLCBdDQpwcmludChteV90aW1lLCBkaWdpdHMgPSA2KQ0KDQpgYGANCg0KIyBQbG90IFJlc3VsdHMNCg0KYGBge3IgYmVuY2g0LCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMH0NCg0KZ2dwbG90bHkoZ2dwbG90KGRhdGEgPSBkYXRhX3RpbWUsIGFlcyh4ID0gVGltZSkpICsgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiksIGJpbnMgPSAyMCwgY29sb3IgPSAiZGFya2JsdWUiLCBmaWxsID0gImxpZ2h0Ymx1ZSIpICsgZmFjZXRfd3JhcCh+IEJlbmNocywgbmNvbCA9IDIpICsgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IHRfbWVhbiksIGNvbG9yID0gImJsdWUiLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMikgKyBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gdF9tZWRpYW4pLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMikgKyBsYWJzKHggPSAiVGltZSAoTWlsbGlzZWNvbmRzKSIsIHkgPSAiRGVuc2l0eSIpICsgdGhlbWVfYncoKSkNCg0KYGBgDQoNCmBgYHtyIGJlbmNoNSwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTB9DQpnZ3Bsb3RseShnZ3Bsb3QoZGF0YSA9IGRhdGFfdGltZVssIC4oVGltZSwgQmVuY2gpXSwgYWVzKHggPSBUaW1lLCB5ID0gLi5jb3VudC4uLCBmaWxsID0gQmVuY2gpKSArIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gLi5kZW5zaXR5Li4pLCBiaW5zID0gMTAwLCBwb3NpdGlvbiA9ICJmaWxsIikgKyBsYWJzKHggPSAiVGltZSAoTWlsbGlzZWNvbmRzKSIsIHkgPSAiRGVuc2l0eSIpICsgdGhlbWVfYncoKSkNCmBgYA0KDQpgYGB7ciBiZW5jaDYsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTEwfQ0KZ2dwbG90bHkoZ2dwbG90KGRhdGEgPSBkYXRhX3RpbWUsIGFlcyh4ID0gVGltZSwgeSA9IC4uY291bnQuLiwgZmlsbCA9IEJlbmNoKSkgKyBnZW9tX2RlbnNpdHkocG9zaXRpb24gPSAiZmlsbCIpICsgbGFicyh4ID0gIlRpbWUgKE1pbGxpc2Vjb25kcykiLCB5ID0gIkRlbnNpdHkiKSArIHRoZW1lX2J3KCkpDQpgYGA=